package xyz.zhzby.cyzx.judge.core;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import xyz.zhzby.cyzx.judge.entity.Checkpoint;
import xyz.zhzby.cyzx.judge.entity.Language;
import xyz.zhzby.cyzx.judge.entity.Submission;
import xyz.zhzby.cyzx.judge.exception.CreateDirectoryException;
import xyz.zhzby.cyzx.judge.service.ICheckpointService;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author zby
 * @date 2022/02/04
 * @Description 预处理器, 用于完成评测前准备工作.
 */
@Component
public class Preprocessor {

    @Autowired
    private ICheckpointService checkpointService;

    @Value("${judger.checkpointDir}")
    private String checkpointDirectory;

    /**
     * @Param [submission, workDirectory, baseFileName] [评测记录对象, 用于产生编译输出的目录, 随机文件名(不包含后缀)]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 创建测试代码至本地磁盘.
     */
    public void createTestCode(Submission submission, String workDirectory, String baseFileName) throws Exception {
        File workDirFile = new File(workDirectory);
        if (!workDirFile.exists() && !workDirFile.mkdirs()) {
            throw new CreateDirectoryException("Failed to create directory: " + workDirectory);
        }
        setWorkDirectoryPermission(workDirFile);

        Language language = submission.getLanguage();
        String code = replaceClassName(language, submission.getCode(), baseFileName);
        String codeFilePath =
                String.format(
                        "%s/%s.%s", workDirectory, baseFileName, getCodeFileSuffix(language));

        FileOutputStream outputStream = new FileOutputStream(codeFilePath);
        IOUtils.write(code, outputStream);
        IOUtils.closeQuietly(outputStream);
    }

    /**
     * @Param [language] [编程语言对象]
     * @Return java.lang.String [代码文件的后缀名]
     * @Author zby
     * @Date 2022/02/04
     * @Description 获取代码文件的后缀名.
     */
    private String getCodeFileSuffix(Language language) {
        String compileCommand = language.getCompileCommand();
        Pattern pattern = Pattern.compile("\\{filename}\\.((?!exe| ).)+");
        Matcher matcher = pattern.matcher(compileCommand);

        if (matcher.find()) {
            String sourceFileName = matcher.group();
            return sourceFileName.replaceAll("\\{filename}\\.", "");
        }
        return "";
    }

    /**
     * @Param [language, code, newClassName] [编程语言对象, 待替换的代码, 新的类名]
     * @Return java.lang.String []
     * @Author zby
     * @Date 2022/02/04
     * @Description 换部分语言中的类名(如Java), 以保证正常通过编译.
     */
    private String replaceClassName(Language language, String code, String newClassName) {
        if (!"Java".equalsIgnoreCase(language.getName())) {
            return code;
        }
        return code.replaceAll("class[ \n]+Main", "class " + newClassName);
    }

    /**
     * @Param [workDirectory] [用于产生编译输出的目录]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 设置代码文件所在目录的读写权限. 在Linux下, 代码以UID=1536的用户运行, 因此需要为Others用户组分配写权限.
     */
    private void setWorkDirectoryPermission(File workDirectory) throws IOException {
        if (!System.getProperty("os.name").contains("Windows")) {
            Set<PosixFilePermission> permissions = new HashSet<>();

            permissions.add(PosixFilePermission.OWNER_READ);
            permissions.add(PosixFilePermission.OWNER_WRITE);
            permissions.add(PosixFilePermission.OWNER_EXECUTE);

            permissions.add(PosixFilePermission.GROUP_READ);
            permissions.add(PosixFilePermission.GROUP_WRITE);
            permissions.add(PosixFilePermission.GROUP_EXECUTE);

            permissions.add(PosixFilePermission.OTHERS_READ);
            permissions.add(PosixFilePermission.OTHERS_WRITE);
            permissions.add(PosixFilePermission.OTHERS_EXECUTE);
            Files.setPosixFilePermissions(workDirectory.toPath(), permissions);
        }
    }

    /**
     * @Param [problemId] [试题ID]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 从数据库抓取评测数据.
     */
    public void fetchTestPoints(Long problemId) throws Exception {
        String checkpointsFilePath =
                String.format("%s/%s", checkpointDirectory, problemId);
        File checkpointsDirFile = new File(checkpointsFilePath);
        if (!checkpointsDirFile.exists() && !checkpointsDirFile.mkdirs()) {
            throw new CreateDirectoryException(
                    "Failed to create the checkpoints directory: " + checkpointsFilePath);
        }

        LambdaQueryWrapper<Checkpoint> qw = new LambdaQueryWrapper<>();
        qw.eq(Checkpoint::getProblemId, problemId);
        List<Checkpoint> checkpoints = checkpointService.list(qw);
        for (Checkpoint checkpoint : checkpoints) {
            Long checkpointId = checkpoint.getId();
            { // Standard Input File
                String filePath =
                        String.format("%s/input#%s.txt", checkpointsFilePath, checkpointId);
                FileOutputStream outputStream = new FileOutputStream(filePath);
                String input = checkpoint.getInput();
                IOUtils.write(input, outputStream);
                IOUtils.closeQuietly(outputStream);
            }
            { // Standard Output File
                String filePath =
                        String.format("%s/output#%s.txt", checkpointsFilePath, checkpointId);
                FileOutputStream outputStream = new FileOutputStream(filePath);
                String output = checkpoint.getOutput();
                IOUtils.write(output, outputStream);
                IOUtils.closeQuietly(outputStream);
            }
        }
    }

    /**
     * @Param [problemId, submissionId, input] [试题ID, 测试提交ID, 用户的测试输入]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 将用户测试输入保存.
     */
    public void fetchInput(Long problemId, Long submissionId, String input) throws Exception {
        String checkpointsFilePath =
                String.format("%s/%s", checkpointDirectory, problemId);
        File checkpointsDirFile = new File(checkpointsFilePath);
        if (!checkpointsDirFile.exists() && !checkpointsDirFile.mkdirs()) {
            throw new CreateDirectoryException(
                    "Failed to create the checkpoints directory: " + checkpointsFilePath);
        }

        {// Standard Input File
            String filePath =
                    String.format("%s/input#%s.txt", checkpointsFilePath, submissionId);
            FileOutputStream outputStream = new FileOutputStream(filePath);
            IOUtils.write(input, outputStream);
            IOUtils.closeQuietly(outputStream);
        }

        { // Standard Output File
            String filePath =
                    String.format("%s/output#%s.txt", checkpointsFilePath, submissionId);
            FileOutputStream outputStream = new FileOutputStream(filePath);
            String output = "";
            IOUtils.write(output, outputStream);
            IOUtils.closeQuietly(outputStream);
        }
    }
}
